package abel.wlmr.server;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import abel.wlmr.config.MsaConfigLoader;
import io.netty.bootstrap.Bootstrap;
import io.netty.bootstrap.ServerBootstrap;
import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelInitializer;
import io.netty.channel.ChannelOption;
import io.netty.channel.EventLoopGroup;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.SocketChannel;
import io.netty.channel.socket.nio.NioDatagramChannel;
import io.netty.channel.socket.nio.NioServerSocketChannel;
import io.netty.handler.timeout.IdleStateHandler;

public class WlmrServer {
    private static final int DEFAULT_WLMR_PORT = 7771;

    private static final Logger logger = LoggerFactory.getLogger(WlmrServer.class);

    private static final String usage =
            "Usage (20170607.1):\njava -jar wlmr_server-1.0.jar [port]\n  port - port number to listen on, default to 7771.\n";

    public static void main(String args[]) throws Exception {
        System.out.println(usage);
        new WlmrServer().run(args);
    }

    public void run(String... args) throws Exception {
        MsaConfigLoader.checkConfigFiles();
        logger.info("main config: {}", MsaConfigLoader.getMsaConfig());

        // start up socket server on certain port.
        final int port;
        if (args.length > 0) {
            port = Integer.parseInt(args[0]);
        } else {
            port = DEFAULT_WLMR_PORT;
        }

        logger.info("Launching WLMR Server On " + port + " ...");

        // UDP
        new Thread(new Runnable() {

            @Override
            public void run() {
                try {
                    logger.info("WLMR UDP server launching on port : " + port);
                    launchWlmrUdpServer(port);
                    logger.info("WLMR UDP server exited normally.");
                } catch (Exception e) {
                    logger.info("WLMR UDP server exited caused by " + e.getMessage(), e);
                }
            }
        }).start();

        launchWlmrTcpServer(port);
    }

    /**
     * @param port
     * @throws InterruptedException
     */
    public void launchWlmrTcpServer(final int port) throws InterruptedException {
        EventLoopGroup bossGroup = new NioEventLoopGroup(); // (1)
        EventLoopGroup workerGroup = new NioEventLoopGroup();
        try {
            ServerBootstrap b = new ServerBootstrap(); // (2)
            b.group(bossGroup, workerGroup)
                .channel(NioServerSocketChannel.class) // (3)
                .childHandler(new ChannelInitializer<SocketChannel>() { // (4)
                    @Override
                    public void initChannel(SocketChannel ch)
                            throws Exception {
                        ch.pipeline().addLast(
                            new IdleStateHandler(2, 3, 4),
                            new WlmrPacketDecoder(),
                            new WlmrPacketEncoder(),
                            new WlmrServerHandler());
                    }
                })
                .option(ChannelOption.SO_BACKLOG, 128) // (5)
                .childOption(ChannelOption.SO_KEEPALIVE, true); // (6)

            // Bind and start to accept incoming connections.
            ChannelFuture f = b.bind(port).sync(); // (7)
            logger.info("Bind to specified port - " + port);

            // Wait until the server socket is closed.
            // In this example, this does not happen, but you can do that to
            // gracefully
            // shut down your server.
            f.channel().closeFuture().sync();
            logger.error("Server exits suprisingly!");
        } finally {
            logger.error("Clean up before server exit.");
            workerGroup.shutdownGracefully();
            bossGroup.shutdownGracefully();
        }
    }

    private void launchWlmrUdpServer(int port) throws Exception {
        Bootstrap b = new Bootstrap();
        EventLoopGroup group = new NioEventLoopGroup();
        b.group(group)
            .channel(NioDatagramChannel.class)
            .option(ChannelOption.SO_BROADCAST, true)
            .handler(new WlmrServerHandler());

        b.bind(port).sync().channel().closeFuture().await();
    }
}
